.. _глава_59_start:

Модули и библиотеки
====================

.. index::
    модуль
    библиотека
    static this
    static ~this

Строительными блоками программ (и библиотек) на языке D являются модули.
Модули D основаны на простой концепции: Каждый исходный файл - это модуль.
Соответственно, все отдельные файлы, в которых мы писали наши программы, были
были отдельными модулями.
По умолчанию модуль называется так же, как и его файл без расширения .d
расширения. При явном указании имя модуля определяется ключевым словом
module, которое должно находиться в первой некомментируемой строке исходного файла
файла.
Например, если имя исходного файла - "cat.d", то имя модуля
модуля будет задано ключевым словом module:

.. code-block:: d

    module cat;

    class Cat
    {
        // ...
    }

Строка модуля необязательна, если модуль не является частью какого-либо пакета (см. ниже).
Если она не указана, она совпадает с именем файла без расширения .d.

**static this() и static ~this()**

static this() и static ~this() в области видимости модуля аналогичны своим
struct и class

.. code-block:: d

    module cat;
    static this()
    {
        // ... the initial operations of the module ...
    }

    static ~this()
    {
        // ... the final operations of the module ...
    }


Код, находящийся в этих диапазонах, выполняется один раз для каждого потока. (Обратите внимание, что большинство
программы состоят из одного потока, который начинает выполнять функцию main()).
Код, который должен выполняться только один раз для всей программы (например, инициализация
общие и неизменяемые переменные) должен быть определен в общих статических переменных this()
и блоках shared static ~this(), которые будут рассмотрены в главе "Совместное использование данных
Взаимодействие (:ref:`Совместное использование данных (Concurrency)<Совместное использование данных (Concurrency)>`)

**Имена файлов и модулей**

D поддерживает Юникод в именах исходного кода и модулей. Однако поддержка Юникода
файловые системы поддерживают Unicode по-разному. Например, хотя большинство файловых систем Linux
поддерживают Юникод, имена файлов в файловых системах Windows могут не различать
между строчными и прописными буквами. Кроме того, большинство файловых систем ограничивают
символов, которые можно использовать в именах файлов и каталогов.
По соображениям переносимости я рекомендую использовать в именах файлов только строчные буквы ASCII
в именах файлов. Например, "resume.d" будет подходящим именем файла для класса
с названием Résumé.
Соответственно, имя модуля также будет состоять из букв ASCII:


.. code-block:: d

    module resume; // Module name consisting of ASCII letters
    class Résumé  // Program code consisting of Unicode characters
    {
        // ...
    }

Пакеты
-------

Совокупность связанных модулей называется пакетом. Пакеты D - это простая
концепцию: Исходные файлы, находящиеся в одном каталоге, считаются
принадлежат одному пакету. Имя каталога становится именем
пакета, которое также должно быть указано в качестве первой части имен модулей.
Например, если файлы "cat.d" и "dog.d" находятся в каталоге "animal", то
указание имени каталога вместе с именем модуля заставляет их быть частью
одного и того же пакета:

.. code-block:: d

    module animal.cat;
    class Cat
    {
    // ...
    }

Аналогично для модуля dog

.. code-block:: d

    module animal.dog;
    class Dog
    {
    // ...
    }

Для модулей, являющихся частями пакетов, строка модуля не является необязательной, и необходимо указывать
должно быть указано все имя модуля, включая имя пакета.
Поскольку имена пакетов соответствуют именам каталогов, имена пакетов
модулей, расположенных глубже одного уровня каталогов, должны отражать эту иерархию. Для
например, если каталог "animal" включает каталог "vertebrate", то имя
модуля, находящегося в этом каталоге, также будет включать vertebrate:

.. code-block:: d

    module animal.vertebrate.cat;

Иерархия каталогов может быть произвольно сложной в зависимости от потребностей
программы. Относительно короткие программы обычно содержат все исходные файлы в
одном каталоге.

.. index::
    import
    импорт модулей

Импортирование модулей
----------------------

Ключевое слово import, которое мы до сих пор использовали почти во всех программах,
предназначено для внедрения модуля в текущий модуль:

.. code-block:: d

    import std.stdio;

Имя модуля может содержать также имя пакета. Например, часть std.
указывает на то, что stdio - это модуль, входящий в пакет std.
Аналогичным образом будут импортированы модули animal.cat и animal.dog. Давайте
предположим, что следующий код находится в файле с именем "deneme.d".

.. code-block:: d

    module deneme; // the name of this module

    import animal.cat; // a module that it uses
    import animal.dog; // another module that it uses

    void main()
    {
        auto cat = new Cat();
        auto dog = new Dog();
    }

*Примечание:Как описано ниже, для правильной сборки программы эти файлы модулей должны быть
также должны быть предоставлены компоновщику.*
Одновременно можно импортировать более одного модуля:

.. code-block:: d

    import animal.cat, animal.dog;

**Избирательный импорт**

Вместо того чтобы импортировать модуль целиком со всеми его именами, можно
импортировать из него только определенные имена.

.. code-block:: d

    import std.stdio : writeln;
    // ...
    writefln("Hello %s.", name); // ← ОШИБКА компиляции

Приведенный выше код не может быть скомпилирован, потому что импортируется только writeln, а не writefln.
Выборочный импорт считается лучше, чем импорт всего модуля.
поскольку это снижает вероятность коллизии имен. Как мы увидим на примере
ниже, столкновение имен может произойти, когда одно и то же имя встречается в более чем одном
импортируемом модуле.
Выборочный импорт также может сократить время компиляции, поскольку компилятору
компилятору нужно компилировать только те части модуля, которые действительно импортируются. С
с другой стороны, выборочный импорт требует больше работы, поскольку каждое импортируемое имя должно быть указано
отдельно в строке импорта.
В этой книге селективный импорт не используется в основном для краткости.

**Локальный импорт**

До сих пор мы всегда импортировали все необходимые модули в верхней части
программы:

.. code-block:: d

    import std.stdio; // ← сверху
    import std.string; // ← сверху
    // ... остальные модули ...

Вместо этого модули можно импортировать в любую другую строку исходного кода. Для
Например, две функции следующей программы импортируют модули, которые
которые им нужны в их собственных областях видимости:

.. code-block:: d

    string makeGreeting(string name)
    {
        import std.string;

        string greeting = format("Hello %s", name);
        return greeting;
    }

    void interactWithUser()
    {
        import std.stdio;

        write("Please enter your name: ");
        string name = readln();
        writeln(makeGreeting(name));
    }

    void main()
    {
        interactWithUser();
    }

Локальный импорт рекомендуется вместо глобального импорта, потому что вместо
импортировать каждый модуль безусловно сверху, компилятор может импортировать только
те, которые находятся в тех областях, которые действительно используются. Если компилятор знает, что
что программа никогда не вызывает функцию, он может игнорировать директивы импорта внутри этой
функции.
Кроме того, локально импортированный модуль доступен только в этой локальной
scope, что еще больше снижает риск столкновения имен.
Позже в главе "Смеси" (:ref:`Примеси(Mixins)<глава_80_start>`) мы увидим, что локальный импорт на самом деле
требуется для шаблонных миксинов.
Примеры, приведенные в этой книге, не используют преимущества локального импорта
в основном потому, что локальный импорт был добавлен в D после начала написания этой книги.

**Расположение модулей**

Компилятор находит файлы модулей, преобразуя имена пакетов и модулей
непосредственно в имена каталогов и файлов.
Например, два предыдущих модуля будут расположены как "animal/cat.d" и
"animal/dog.d", соответственно (или "animal\cat.d" и "animal\dog.d", в зависимости от
файловой системы). Если учесть еще и основной исходный файл, то приведенная выше программа
состоит из трех файлов.


**Длинные и короткие имена модулей**

Имена, которые используются в программе, могут быть написаны с помощью имен модулей и
именами пакетов:

.. code-block:: d

    auto cat0 = Cat();
    auto cat1 = animal.cat.Cat(); // то же, что и выше

Обычно длинные имена не нужны, но иногда возникают конфликты имен.
Например, при обращении к имени, которое встречается более чем в одном модуле,
компилятор не может определить, какой из них имеется в виду.
В следующей программе длинные имена пишутся для того, чтобы различить
двумя отдельными структурами Jaguar, которые определены в двух отдельных модулях: animal
и car

.. code-block:: d

    import animal.jaguar;
    import car.jaguar;
    // ...
    auto conflicted =  Jaguar(); // ← compilation ERROR
    auto myAnimal = animal.jaguar.Jaguar(); // ← compiles
    auto myCar    =    car.jaguar.Jaguar(); // ← compiles


**Переименование при импорте**

Можно переименовывать импортируемые модули как для удобства, так и для разрешения
конфликтов имен:

.. code-block:: d

    import carnivore = animal.jaguar;
    import vehicle = car.jaguar;
    // ...
    auto myAnimal = carnivore.Jaguar(); // ← compiles
    auto myCar    = vehicle.Jaguar(); // ← compiles

Вместо того чтобы переименовывать весь импорт, можно переименовать отдельные
импортированные символы.
Например, при компиляции следующего кода с ключом компилятора -w,
компилятор предупредит, что вместо функции sort() следует использовать функцию
свойства .sort:

.. code-block:: console

    Warning: use std.algorithm.sort instead of .sort property

*Note:Thearr.sort expression above is the equivalent ofsort(arr) but it is written
in the UFCS syntax, which we will see ina later chapter.* (:ref:`Универсальный синтаксис вызова функций (UFCS)<глава_61_start>`)

Одно из возможных решений - это импорт модуля std.algorithm.sort и переименование при импорте.
Новое имя algSort будет равнозначно имени sort, предупреждение компилятора больше не будут выдаваться

.. code-block:: d

    import std.stdio;
    import std.algorithm : algSort = sort;

    void main()
    {
        auto arr = [ 2, 10, 1, 5 ];
        arr.algSort;
        writeln(arr);
    }

.. index::
    package
    пакет

**импортирование пакета как модуля**

Иногда требуется импортировать несколько модулей пакета вместе. Например,
например, при импорте одного модуля из пакета animal все
другие модули тоже должны быть импортированы: animal.cat, animal.dog,
animal.horse и т. д.
В таких случаях можно импортировать некоторые или все модули пакета путем
импортируя пакет, как если бы он был модулем:

.. code-block:: d

    import animal; // ← entire package imported as a module

Это достигается с помощью специального файла конфигурации в каталоге package, который должен
всегда иметь имя package.d. Этот специальный файл включает в себя директиву модуля для
пакета и импортирует модули пакета публично:

.. code-block:: d

    // The contents of the file animal/package.d:
    module animal;
    public import animal.cat;
    public import animal.dog;
    public import animal.horse;
    // ... same for the other modules ..

Публичный импорт модуля делает этот модуль доступным для пользователей
импортируемого модуля. В результате, когда пользователи импортируют только модуль animal
модуль (который на самом деле является пакетом), они получают доступ к animal.cat и всем
другим модулям

**Утратившие актуальность (устаревшие)  функции**

Модули развиваются со временем и выходят под новыми номерами версий. Переходя по адресу
начиная с определенной версии, авторы модуля могут решить
отказаться от некоторых функций. Утрата функции означает, что вновь написанные
программы больше не должны полагаться на эту функцию; использование устаревшей функции
не одобряется. В будущем устаревшие функции могут быть даже удалены из модуля.
в будущем.
Причин, по которым функция устаревает, может быть много. Например, новая
новая версия модуля может включать лучшую альтернативу, функция может быть
перемещена в другой модуль, название функции изменилось, чтобы быть
чтобы соответствовать остальной части модуля, и т. д.
Утрата функциональности становится официальной, определяя ее с помощью атрибута deprecated
атрибутом deprecated и, по желанию, пользовательским сообщением. Например, следующее
сообщение deprecation сообщает пользователю, что имя функции было
было изменено:

.. code-block:: d

    deprecated("Please use doSomething() instead.")
    void do_something()
    {
    // ...
    }

Указав один из следующих опций компилятора, пользователь модуля может
определить, как компилятор должен реагировать на использование устаревшей функции:
∙-d: Использование устаревших функций должно быть разрешено
∙-dw: Использование устаревших функций должно выдавать предупреждения о компиляции
∙-de: Использование устаревших функций должно приводить к ошибкам компиляции
Например, вызов устаревшей функции в программе и её компиляция с -de приведет к ошибке компиляции

.. code-block:: d

    do_something();

.. code-block:: console

    $ dmd deneme.d -de
        deneme.d: Deprecation: function deneme.do_something is deprecated
        Please use doSomething() instead.

Имя устаревшей функции обычно определяется как псевдоним нового имени:


.. code-block:: d

    deprecated("Please use doSomething() instead.")
    alias do_something = doSomething;
    void doSomething()
    {
    // ...
    }

С ключевым словом alias мы познакомимся в одной из следующих глав (:ref:`alias и with<глава_66_start>`).

**Добавление определений модулей в программу**

Ключевого слова import недостаточно для того, чтобы модули стали частью
программы. Оно просто делает доступными возможности модуля внутри текущего
модуля. Это необходимо только для компиляции кода.
Невозможно собрать предыдущую программу только по основному исходному файлу,
"deneme.d":

.. code-block:: console

    $ dmd deneme.d -w -de
    deneme.o: In function `_Dmain':
    deneme.d: undefined reference to `_D6animal3cat3Cat7__ClassZ'
    deneme.d: undefined reference to `_D6animal3dog3Dog7__ClassZ'
    collct2: ld returned 1 exit status

Эти сообщения об ошибках генерируются компоновщиком. Хотя они не являются удобными для пользователя
дружественные сообщения, они указывают на то, что некоторые определения, необходимые программе
отсутствуют.
За сборку программы отвечает компоновщик, который
который автоматически вызывается компилятором за кулисами. Компилятор передает
модули, которые он только что скомпилировал, компоновщику, а тот объединяет эти
модули (и библиотеки) для создания исполняемой программы.
По этой причине все модули, из которых состоит программа, должны быть предоставлены
компоновщику. Чтобы приведенная выше программа была собрана, "animal/cat.d" и "animal/dog.d"
также должны быть указаны в строке компиляции:

.. code-block:: console

    $ dmd deneme.d animal/cat.d animal/dog.d -w -de

Вместо того чтобы каждый раз упоминать модули по отдельности в командной строке
командной строки, их можно объединить в библиотеки.

Библиотеки
----------

Коллекция скомпилированных модулей называется библиотекой. Библиотеки не являются программами
сами по себе; в них нет функции main(). Библиотеки содержат скомпилированные
определения функций, структур, классов и других свойств модулей, которые
которые позже будут соединены компоновщиком для создания программы.
Опция командной строки dmd -lib предназначена для создания библиотек. Следующая
команда создает библиотеку, содержащую модули "cat.d" и "dog.d". Адрес
имя библиотеки задается с помощью ключа -of:

.. code-block:: console

    dmd animal/cat.d animal/dog.d -lib -ofanimal -w -de

Фактическое имя файла библиотеки зависит от платформы. Например.
расширение библиотечных файлов - .a в системах Linux: animal.a.
После того как библиотека собрана, нет необходимости указывать модули "animal/cat.d" и
"animal/dog.d" по отдельности. Достаточно файла библиотеки:

.. code-block:: console

    dmd deneme.d animal.a -w -de

Приведенная выше команда заменяет следующую:

.. code-block:: console

    $ dmd deneme.d animal/cat.d animal/dog.d -w -de

В качестве исключения, стандартную библиотеку D Phobos не нужно указывать в командной строке.
командной строке. Эта библиотека автоматически включается за кулисами.
В противном случае ее можно указать, как показано в следующей строке:

.. code-block:: console

    $ dmd deneme.d animal.a /usr/lib64/libphobos2.a -w -de

Примечание: имя и расположение библиотеки Phobos может отличаться в разных системах.
системах.


**Использование библиотек других языков**

В D можно использовать библиотеки, написанные на других компилируемых языках, таких как C и
C++. Однако, поскольку в разных языках используются разные связи, такие библиотеки
доступны для D-кода только через их D-биндинги.
биндинги - это набор правил, определяющих доступность объектов в
библиотеки, а также то, как имена (символы) этих сущностей представляются в
компилируемом коде. Имена в скомпилированном коде отличаются от имен, которые
программист пишет в исходном коде: Скомпилированные имена изменяются
в соответствии с правилами конкретного языка или компилятора.
Например, в соответствии со связкой языка C, имя функции foo в языке C будет
с ведущим подчеркиванием в виде _foo в скомпилированном коде. Искажение имен
более сложным в таких языках, как C++ и D, поскольку эти языки позволяют использовать
одно и то же имя для различных объектов в разных модулях, структурах, классах и т. д., а также для перегрузки функций.
а также для перегрузок функций. Функцию D с именем foo в исходном коде необходимо
должна быть преобразована таким образом, чтобы отличить ее от всех других имен foo, которые только могут быть.
существующих в программе. Хотя точные искаженные имена обычно не важны
для программиста, модуль core.demangle может быть использован для искажения и
деманглирования символов:

.. code-block:: d

    module deneme;
    import std.stdio;
    import core.demangle;
    void foo()
    {
    }

    void main()
    {
        writeln(mangle!(typeof(foo))("deneme.foo"));
    }

Примечание:mangle() - это функция-шаблон, синтаксис которой на данном этапе книги незнаком.
Мы познакомимся с шаблонами позже в главе "Шаблоны" (:ref:`Шаблоны<глава_64_start>`).
Функция, имеющая тот же тип, что и foo, и названная deneme.foo,
в скомпилированном коде имеет следующее искаженное имя:

.. code-block:: console

    _D6deneme3fooFZv

Искажение имен - это причина, по которой сообщения об ошибках компоновщика не могут содержать дружественные пользователю
дружественные пользователю имена.
Например, символ в сообщении об ошибке выше был_D6animal3cat3Cat7__ClassZ вместо animal.cat.Cat.
Атрибут extern() задает связь между сущностями. Допустимые типы связей
которые можно использовать с атрибутом extern(), являются C, C++, D, Objective-C, Pascal, System и
Windows. Например, если в коде на языке D необходимо вызвать функцию, которая
определенной в библиотеке C, эта функция должна быть объявлена как определенная  C.

.. code-block:: d


    // Declaring that 'foo' has C linkage (e.g. it may be defined
    // in a C library):
    extern(C) void foo();
    void main()
    {
        foo(); // this call would be compiled as a call to '_foo'
    }

При связывании в C++ пространство имен, в котором определено имя, указывается как
второй аргумент атрибута extern().
Например, согласно
следующему объявлению, bar() - это объявление функции a::b::c::bar()определенной в библиотеке C++ (обратите внимание, что в коде D вместо двоеточий используются точки):

.. code-block:: d

    // Declaring that 'bar' is defined inside namespace a::b::c
    // and that it has C++ linkage:
    extern(C++, a.b.c) void bar();
    void main()
    {
        bar(); // a call to a::b::c::bar()
        a.b.c.bar(); // same as above
    }

Файл, содержащий такие D-объявления возможностей внешней библиотеки, называется
называется D привязкой этой библиотеки.
К счастью, в большинстве случаев программистам не нужно
писать их с нуля, поскольку D-биндинги для многих популярных не-D библиотекдоступны в рамках проекта Deimos1.
Когда атрибут extern используется без типа связи, он имеет другое
значение: Он указывает, что за хранение переменной отвечает внешняя библиотека; компилятор D не должен резервировать для нее место в этом модуле.
Имея разные значения, extern и extern() могут использоваться вместе:

.. code-block:: d

    // Declaring that the storage for 'g_variable' is already
    // defined in a C library:
    extern(C) extern int g_variable;

Если бы атрибут extern не был указан выше, то при наличии связи с C,
g_variable была бы переменной этого модуля D.


